Skip to content

fix: force LF on shell scripts so shebangs don't break (CRLF) - #2

Merged
d-morrison merged 2 commits into
mainfrom
fix/sh-eol-lf-1
Jun 26, 2026
Merged

fix: force LF on shell scripts so shebangs don't break (CRLF)#2
d-morrison merged 2 commits into
mainfrom
fix/sh-eol-lf-1

Conversation

@d-morrison

Copy link
Copy Markdown
Collaborator

Closes #1.

A shell script checked out with Windows CRLF line endings gets a #!/bin/bash\r shebang; the kernel then looks for the interpreter /bin/bash\r (shown as /bin/bash^M) and fails with bad interpreter: No such file or directory. core.autocrlf normalizes CRLF only on commit, never on checkout, so a working tree that picks up CRLF stays broken.

This adds .gitattributes with *.sh text eol=lf, which forces LF in the working tree on every checkout regardless of each user's global git config. No committed shell scripts in this repo currently have CRLF, so this is purely preventive — no file renormalization.

A CRLF in a checked-out shell script makes its shebang resolve to the
interpreter "/bin/bash\r", which does not exist, so the script fails with
"bad interpreter". core.autocrlf normalizes only on commit, not checkout;
an explicit eol=lf attribute forces LF in the working tree on every
checkout regardless of each user's global git config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown

Claude finished @d-morrison's task in 1m 41s —— View job


Code Review — PR #2: force LF on shell scripts

  • Gather context
  • Read changed file and inspect repo shell scripts
  • Review .gitattributes for correctness and completeness
  • Post review findings

Overall verdict: looks good — approve when ready.

The fix is correct, well-scoped, and the PR description is thorough. The *.sh text eol=lf attribute is the right tool for this problem: it forces LF in the working tree on every checkout, regardless of each contributor's global core.autocrlf setting. The existing .github/scripts/apply-rulesets.sh already has LF line endings, so no renormalization is triggered.

Checklist:

Check Result
Fix is correct (text eol=lf forces LF on checkout)
Existing shell script already has LF (no renorm needed)
In-file comment explains the why clearly
No generated files (_manuscript/, _freeze/) touched
Template impact: downstream repos inherit the protection
Quarto/R/prose files unaffected

One optional suggestion posted as an inline comment on .gitattributes lines 1–3: prepend * text=auto for broader line-ending normalization across all text files (YAML, R, Quarto, etc.). Not blocking — the current change fully addresses the stated issue.

Comment thread .gitattributes
Comment on lines +1 to +3
# Shell scripts must use LF line endings — a CRLF on the shebang line makes
# the kernel look for "/bin/bash\r" and fail with "bad interpreter".
*.sh text eol=lf

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional enhancement: Consider prepending a * text=auto rule before the *.sh line. It's the standard first line in .gitattributes — it tells git to auto-detect text files and normalize their line endings on commit. Combined with the explicit *.sh text eol=lf override, this gives blanket protection to all text files (YAML, R, Quarto, etc.) while still guaranteeing LF in working-tree shell scripts.

Suggested change
# Shell scripts must use LF line endings — a CRLF on the shebang line makes
# the kernel look for "/bin/bash\r" and fail with "bad interpreter".
*.sh text eol=lf
# Auto detect text files and perform LF normalization
* text=auto
# Shell scripts must use LF line endings — a CRLF on the shebang line makes
# the kernel look for "/bin/bash\r" and fail with "bad interpreter".
*.sh text eol=lf

Not blocking — the current rule already solves the stated problem. The only downside to text=auto is it may trigger renormalization of other tracked files on first checkout for contributors; run git add --renormalize . locally to see if anything would change before merging.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adopted, with one tweak: used * text=auto eol=lf rather than bare * text=auto, so LF is actually forced in the working tree on checkout (bare text=auto leaves native EOL, i.e. CRLF on Windows) — this matches the repo owner's global gitattributes. Verified zero renormalization churn: git grep -lI $'\r' origin/main returns nothing and git add --renormalize . stages only .gitattributes. Worth noting this repo's working tree already has 19 CRLF files including 6 Python/R scripts whose shebangs would break the same way the .sh did, so the blanket rule is the better fix here.

Prepend `* text=auto eol=lf` so every text file (not just shell scripts)
is forced to LF in the working tree on checkout. qmt's working tree
already accumulates CRLF in non-shell scripts (.github/scripts/*.py, .lua,
.css, .yml); a CRLF Python script breaks its shebang exactly like the .sh
case did. No committed blobs change (verified zero renormalization churn).

Addresses the reviewer's optional suggestion, using eol=lf (matching the
repo owner's global gitattributes) rather than bare text=auto so LF is
actually forced on checkout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown

Claude finished @d-morrison's task in 49s —— View job


Code Review — PR #2 (updated): broaden .gitattributes to normalize all text files to LF

  • Gather context
  • Read updated .gitattributes
  • Assess * text=auto eol=lf vs bare * text=auto
  • Post review

Overall verdict: approve. The tweak is the right call.

d-morrison's choice of * text=auto eol=lf over bare * text=auto is technically superior for this repo's goals:

Rule LF in repo (on commit) LF in working tree (on checkout)
* text=auto ❌ — native EOL (CRLF on Windows)
* text=auto eol=lf ✅ — forced LF everywhere

Since the original problem was CRLF in the working tree breaking shebangs, forcing eol=lf globally is the correct fix — and it was already verified to cause zero renormalization churn.

On the *.sh text eol=lf line being kept:

It's not redundant — it's belt-and-suspenders in a useful way. The bare text attribute (no =auto) means git unconditionally treats .sh files as text regardless of content heuristics, whereas text=auto applies only when git's heuristic agrees. Keeping the explicit override is good documentation and slightly stricter protection for shell scripts specifically.

File as merged looks good:

* text=auto eol=lf      ← forces LF in working tree for all auto-detected text files
*.sh text eol=lf        ← unconditionally marks .sh as text + LF (belt-and-suspenders)

No issues. Ready to merge.

@d-morrison
d-morrison merged commit e23d2fd into main Jun 26, 2026
10 checks passed
@d-morrison
d-morrison deleted the fix/sh-eol-lf-1 branch June 26, 2026 23:20
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-26 16:22 PDT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Force LF line endings on shell scripts (.gitattributes)

1 participant